package org.springframework.boot.autoconfigure.klock.util;

import lombok.Builder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.klock.core.KlockHandler;
import org.springframework.boot.autoconfigure.klock.core.LockInfoProvider;
import org.springframework.boot.autoconfigure.klock.model.LockInfo;
import org.springframework.boot.autoconfigure.klock.model.LockTimeoutStrategy;
import org.springframework.boot.autoconfigure.klock.model.LockType;
import org.springframework.boot.autoconfigure.klock.model.ReleaseTimeoutStrategy;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.function.Supplier;

/**
 * 与klock语义一致的手动加锁工具
 * @Author ygm
 * @Date 2022/7/24
 */
@Slf4j
@Component
public class KlockUtil extends KlockHandler {

    /**
     * @param name
     * @param key
     * @param runnable
     */
    public static void executeWithDefaultConfig(String name, String key, Runnable runnable) {
        KLockInfo lockInfo = KLockInfo.builder()
                .name(name)
                .keys(new String[]{key})
                .build();
        executeWithKlock(lockInfo, runnable);
    }

    /**
     * @param name
     * @param keys
     * @param runnable
     */
    public static void executeWithDefaultConfig(String name, String[] keys, Runnable runnable) {
        KLockInfo lockInfo = KLockInfo.builder()
                .name(name)
                .keys(keys)
                .build();
        executeWithKlock(lockInfo, runnable);
    }

    /**
     * @param name
     * @param key
     * @param supplier
     * @param <T>
     * @return
     */
    public static <T> T executeWithDefaultConfig(String name, String key, Supplier<T> supplier) {
        KLockInfo lockInfo = KLockInfo.builder()
                .name(name)
                .keys(new String[]{key})
                .build();
        return executeWithKlock(lockInfo, supplier);
    }

    /**
     * @param name
     * @param keys
     * @param supplier
     * @param <T>
     * @return
     */
    public static <T> T executeWithDefaultConfig(String name, String[] keys, Supplier<T> supplier) {
        KLockInfo lockInfo = KLockInfo.builder()
                .name(name)
                .keys(keys)
                .build();
        return executeWithKlock(lockInfo, supplier);
    }

    /**
     * @param lockInfo
     * @param runnable
     */
    public static void executeWithKlock(KLockInfo lockInfo, Runnable runnable) {
        executeWithKlock(lockInfo, (Supplier) () -> {
            runnable.run();
            return null;
        });
    }

    public static <T> T executeWithKlock(KLockInfo klockInfo, Supplier<T> supplier) {
        LockInfo lockInfo = convertLockInfo(klockInfo);
        LockRes lockRes = getLock(lockInfo);
        try {
            boolean acquiredResult = lockRes.getLock().acquire();
            //如果获取锁失败了，则进入失败的处理逻辑
            if (!acquiredResult) {
                if (log.isWarnEnabled()) {
                    log.warn("Timeout while acquiring Lock({})", lockInfo.getName());
                }
                //否则执行预定义的执行策略
                //注意：如果没有指定预定义的策略，默认的策略为静默啥不做处理
                klockInfo.getLockTimeoutStrategy().handle(lockInfo, lockRes.getLock(), null);
            }
            lockRes.countUp();
            return supplier.get();
        } finally {
            //release lock
            log.debug("executeWithKlock release lock " + lockInfo.getCurrentLockId());
            lockRes.countDown();
            if (lockRes.getLevelCount() <= 0) {
                boolean releaseRes = lockRes.getLock().release();
                // avoid release lock twice when exception happens below
                if (!releaseRes) {
                    if (log.isWarnEnabled()) {
                        log.warn("Timeout while release Lock({})", lockInfo.getName());
                    }
                    klockInfo.getReleaseTimeoutStrategy().handle(lockInfo);
                }
            }
            //clean lock
            cleanUpThreadLocal(lockInfo);
        }
    }


    private static LockInfo convertLockInfo(KLockInfo lockInfo) {
        initDefaultValue(lockInfo);
        if (StringUtils.isBlank(lockInfo.getName())) {
            throw new IllegalArgumentException("必须指定锁的名称");
        }
        String unionKey = org.springframework.util.StringUtils.collectionToDelimitedString(
                Arrays.asList(lockInfo.getKeys()), "", "-", "");
        String lockName = LockInfoProvider.buildLockName(lockInfo.getName(),unionKey) ;
        long waitTime = getWaitTime(lockInfo.getWaitTime());
        long leaseTime = getLeaseTime(lockInfo.getLeaseTime());
        LockInfo newLockInfo = new LockInfo();
        newLockInfo.setType(lockInfo.getType());
        newLockInfo.setName(lockName);
        newLockInfo.setWaitTime(waitTime);
        newLockInfo.setLeaseTime(leaseTime);
        return newLockInfo;
    }

    private static void initDefaultValue(KLockInfo lockInfo) {
        if (lockInfo.getType() == null) {
            lockInfo.setType(LockType.Reentrant);
        }
        if (lockInfo.getLockTimeoutStrategy() == null) {
            lockInfo.setLockTimeoutStrategy(LockTimeoutStrategy.KEEP_ACQUIRE);
        }
        if (lockInfo.getReleaseTimeoutStrategy() == null) {
            lockInfo.setReleaseTimeoutStrategy(ReleaseTimeoutStrategy.NO_OPERATION);
        }
        if (lockInfo.getWaitTime() == null) {
            lockInfo.setWaitTime(Long.MIN_VALUE);
        }
        if (lockInfo.getLeaseTime() == null) {
            lockInfo.setLeaseTime(Long.MIN_VALUE);
        }
    }

    @Data
    @Builder
    public static class KLockInfo {
        private LockType type;
        private String name;
        private Long waitTime;
        private Long leaseTime;
        private String[] keys;
        private LockTimeoutStrategy lockTimeoutStrategy;
        private ReleaseTimeoutStrategy releaseTimeoutStrategy;
    }

    private static long getWaitTime(long waitTime) {
        return waitTime == Long.MIN_VALUE ?
                klockConfig.getWaitTime() : waitTime;
    }

    private static long getLeaseTime(long leaseTime) {
        return leaseTime == Long.MIN_VALUE ?
                klockConfig.getLeaseTime() : leaseTime;
    }
}
